package cn.mesmile.admin.modules.system.controller;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.mesmile.admin.common.excel.EasyExcelUtil;
import cn.mesmile.admin.common.limit.LimiterModeEnum;
import cn.mesmile.admin.common.limit.RateLimiter;
import cn.mesmile.admin.common.lock.RedisLock;
import cn.mesmile.admin.common.oss.OssBuilder;
import cn.mesmile.admin.common.oss.domain.AdminFile;
import cn.mesmile.admin.common.rabbit.constant.RabbitConstant;
import cn.mesmile.admin.common.repeat.RepeatSubmit;
import cn.mesmile.admin.common.result.R;
import cn.mesmile.admin.common.utils.AdminRedisTemplate;
import cn.mesmile.admin.common.utils.ResourceI18nUtil;
import cn.mesmile.admin.modules.message.operational.ISendService;
import cn.mesmile.admin.modules.message.vo.MqMessageVO;
import cn.mesmile.admin.modules.system.entity.Sys;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author zb
 * @Description
 */
@Api(tags = "hello world 测试接口")
@Slf4j
@RequestMapping("/api/v1/hello")
@RestController
public class HelloController {

    @Resource
    private AdminRedisTemplate adminRedisTemplate;

    @Resource
    private RabbitTemplate rabbitTemplate;

    @Resource
    private ISendService sendService;

    /**
     *  客户端工具：https://mqttx.app/
     */
    @GetMapping("/message")
    public R message(){
        MqMessageVO mqMessageVO = new MqMessageVO();
        mqMessageVO.setReceiveUser("123");
        mqMessageVO.setContent("设置内容11111111111");
        sendService.sendSingleMessage("123",mqMessageVO);
        return R.data("test");
    }

    @GetMapping("/direct")
    public R send(){
        rabbitTemplate.convertAndSend(RabbitConstant.DIRECT_MODE_EXCHANGE_ONE,
                RabbitConstant.DIRECT_MODE_QUEUE_ONE,"direct message");
        return R.data("发送成功direct");
    }

    @GetMapping("/fanout")
    public R fanout(){
        rabbitTemplate.convertAndSend(RabbitConstant.FANOUT_MODE_EXCHANGE_ONE,
                "","fanout message");
        return R.data("发送成功fanout");
    }

    @GetMapping("/topic")
    public R topic(){
        rabbitTemplate.convertAndSend(RabbitConstant.TOPIC_MODE_EXCHANGE_ONE,
                "topic.mode.test.multi","topic message");
        return R.data("发送成功topic");
    }

    @GetMapping("/deadLetter")
    public R deadLetter(){
        rabbitTemplate.convertAndSend(RabbitConstant.TOPIC_MODE_EXCHANGE_TWO,
                "topic.dead.letter.test","deadLetter message "+ DateUtil.now());
        return R.data("发送成功topic");
    }

    @GetMapping("/delay")
    public R delay(){
        rabbitTemplate.convertAndSend(RabbitConstant.DELAY_MODE_EXCHANGE,
                RabbitConstant.DELAY_MODE_QUEUE, "delay message, delay 8s, " + DateUtil.now(),
                    message -> {
                        message.getMessageProperties().setHeader("x-delay", 8000);
                        return message;
                    }
                );
        return R.data("发送成功delay");
    }


    @RateLimiter(max = 3,limiterMode = LimiterModeEnum.LIMITER_ALL)
    @RepeatSubmit(interval = 20000, param = "#word")
    @ApiOperation(value = "用户登录测试接口")
    @ApiImplicitParams({
            @ApiImplicitParam(name="word",value="关键字备注",required = true,type="Integer")
    })
    @GetMapping("/get")
    public R hello (@RequestParam("word") String word) {
        return R.data(word);
    }


    @RedisLock(value = "buy",param = "#request.getLocalPort()")
    @RepeatSubmit(interval = 20000)
    @GetMapping("/buy")
    public R buy (HttpServletRequest request){
        int localPort = request.getLocalPort();
        log.info(">>>>>>>>>>>>>>  我的端口号是："+ localPort);
        Integer i = adminRedisTemplate.get("stock");
        if (i != null && i > 0) {
            Long lastNumber = adminRedisTemplate.decr("stock");
            log.info(">>>>>>>>>>>>>>> 扣除成功，剩余库存："+ lastNumber);
        } else {
            log.info(">>>>>>>>>>>>>>> 扣除失败库存失败");
        }
        return R.data("success "+ localPort);
    }

    @RateLimiter(param = "#s.hello")
    @RepeatSubmit(interval = 20000, param = "#s.hello;#s.world")
    @PostMapping("/test")
    public R submit(@RequestBody Sys s){
        System.out.println("s = " + s);
        return R.data(s);
    }

    @PostMapping("/upload")
    public R upload(@RequestParam("files") MultipartFile files){
        AdminFile adminFile = OssBuilder.build().putFile(files);
        return R.data(adminFile);
    }

    /**
     * localhost:8080/api/v1/hello/down?fileName=/upload/202208/20220806/am.pak
     */
    @GetMapping("/download")
    public void download(@RequestParam("fileName") String fileName,HttpServletResponse response){
        OssBuilder.build().download(response, fileName);
    }

    /**
     * localhost:8080/api/v1/hello/down?fileName=/upload/202208/20220806/am.pak
     *  localhost:8080/api/v1/hello/download?fileName=/upload/202208/MongoDB教程全集 01-课程介绍.flv
     *  localhost:8080/api/v1/hello/downloadOffset?fileName=/upload/202208/MongoDB教程全集 01-课程介绍.flv
     */
    @GetMapping("/downloadOffset")
    public void downloadOffset(@RequestParam("fileName") String fileName,@RequestParam(value = "offset",required = false) Long offset,
                               @RequestParam(value = "length",required = false) Long length,
                               HttpServletResponse response,HttpServletRequest request) throws Exception {
        // 请求头 Range: bytes=0-1024 表示此次客户端向客户端请求 0到1024范围内的数据
        String range = request.getHeader("Range");
        if (StrUtil.isNotBlank(range)){
            String offsetStr = StrUtil.subBetween(range, "=", "-");
            String lengthStr = StrUtil.subAfter(range, "-", true);
            if (NumberUtil.isNumber(offsetStr)) {
                OssBuilder.build().download(response, fileName, Long.parseLong(offsetStr), Convert.toLong(lengthStr)) ;
            }else {
                OssBuilder.build().download(response, fileName, offset, length) ;
            }
        }

//        // 分段读取本地文件
//        File file = new File("");
//        long fileLength = file.length();
//        // 读取本地文件
//        RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r");
//        // todo 从请求头里面读取 起始位置
//        // 设置起始位置
//        randomAccessFile.seek( offset );
//        // 每次读取 2M
//        byte[] bytes = new byte[1024 * 1024 * 2];
//        int read = randomAccessFile.read(bytes);
//        response.setHeader("Content-Range","bytes "+ offset +"-"+ (fileLength - 1) +"/"+ fileLength);
//        response.setContentLength(read);
//        // 部分内容返回状态码 206 ， 浏览器会自动开启分段式播放，在每次http请求头中加入Range请求头
//        response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
//        response.setContentType(ContentType.OCTET_STREAM.getValue());
//        ServletOutputStream outputStream = response.getOutputStream();
//        outputStream.write(bytes, 0, read);
//        outputStream.close();
//        randomAccessFile.close();
    }

    /**
     * localhost:8080/api/v1/hello/preview?fileName=/upload/202208/20220806/am.pak
     *
     */
    @Deprecated
    @GetMapping("/preview")
    public R preview(@RequestParam("fileName") String fileName){
        String preview = OssBuilder.build().preview(fileName);
        return R.data(preview);
    }

    @GetMapping("/i18")
    public R get(){
        // 参数优先 admin_language   默认根据请求头判断 Accept-Language
        String username = ResourceI18nUtil.getValueByKey("user.login.username");
        return R.data(username);
    }

    @PostMapping("/excel")
    public R excel(@RequestParam("file") MultipartFile file){
        EasyExcelUtil.readAndSave(file,System.out::println,Sys.class);
        return R.data("success");
    }

    @PostMapping("/read")
    public R read(@RequestParam("file") MultipartFile file){
        List<Sys> read = EasyExcelUtil.read(file, Sys.class);
        return R.data(read);
    }

    @GetMapping("/export")
    public void export(HttpServletResponse response){
        ArrayList<Sys> sysList = new ArrayList<>();
        sysList.add(new Sys("数据①","word数据1"));
        sysList.add(new Sys("数据②","word数据2"));
        EasyExcelUtil.export(response,"导出测试", sysList, Sys.class);
    }

    @GetMapping("/export2")
    public void export2(HttpServletResponse response) throws IOException {
        ArrayList<Sys> sysList = new ArrayList<>();
        sysList.add(new Sys("数据①","word数据1"));
        sysList.add(new Sys("数据②","word数据2"));
        EasyExcelUtil.exportAndWatermark(response,"导出测试", sysList, Sys.class,"我是水印IAmWatermark");
    }

    @GetMapping("/trance")
    public R trance(HttpServletRequest request){
        String requestedSessionId = request.getRequestedSessionId();
        int i = 100;
        log.info("before: {}", i);
        try {
            Thread.sleep(i);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return R.data(requestedSessionId);
    }


    /**
     * 程序运行在 Tomcat 中，执行程序的线程是 Tomcat 的工作线程，而 Tomcat 的工作线程是基于线程池的
     * 线程池会【重用】固定的几个线程，一旦线程重用，那么很可能首次从 ThreadLocal 获取的值是之前其他用户的请求遗留的值。
     * 这时，ThreadLocal 中的用户信息就是其他用户的信息
     *
     * 因为线程的创建比较昂贵，所以 Web 服务器往往会使用线程池来处理请求，这就意味着线程会被【重用】。这时，
     * 使用类似 ThreadLocal 工具来存放一些数据时，需要特别注意在代码运行完后，【显式地去清空】设置的数据。
     * 如果在代码中使用了自定义的线程池，也同样会遇到这个问题。
     */
    private static final ThreadLocal<Integer> currentUser = ThreadLocal.withInitial(() -> null);

    @GetMapping("wrong")
    public Map wrong(@RequestParam("userId") Integer userId) {
        //设置用户信息之前先查询一次ThreadLocal中的用户信息
        String before  = Thread.currentThread().getName() + ":" + currentUser.get();
        //设置用户信息到ThreadLocal
        log.info("currentId:{}", userId);
        currentUser.set(userId);
        try {
            //设置用户信息之后再查询一次ThreadLocal中的用户信息
            String after  = Thread.currentThread().getName() + ":" + currentUser.get();
            //汇总输出两次查询结果
            Map result = new HashMap();
            result.put("before", before);
            result.put("after", after);
            return result;
        }finally {
            // 【显式地去清空】设置的数据
            currentUser.remove();
        }
    }


}
